#include "i2c.h"
#include "main.h"

// 延时函数，根据系统时钟调整
static void i2c_delay(void)
{
    volatile uint8_t i = 5; // 可根据实际情况调整延时时间
    while (i--)
        ;
}

// 配置SCL为开漏输出
void SCL_OD(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SCL;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
    GPIO_InitStruct.Pull = GPIO_PULLUP;         // 上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(I2C_PORT, &GPIO_InitStruct);
}

// 配置SDA为推挽输出
void SDA_PP(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SDA;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
    GPIO_InitStruct.Pull = GPIO_PULLUP;         // 上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(I2C_PORT, &GPIO_InitStruct);
}

// 配置SDA为输入
void SDA_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SDA;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;     // 上拉
    HAL_GPIO_Init(I2C_PORT, &GPIO_InitStruct);
}

// I2C GPIO初始化
void I2C_GPIO_Init(void)
{
    // 使能GPIOB时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // 初始化SCL和SDA为高电平
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET);
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET);

    SCL_OD(); // SCL配置为开漏输出
    SDA_PP(); // SDA初始配置为推挽输出

    i2c_delay();
}

// 产生I2C起始信号
void i2c_start(void)
{
    I2C_GPIO_Init();
    SDA_PP(); // SDA设为输出
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET);
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET);
    i2c_delay();

    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_RESET); // SDA由高变低
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低，准备发送数据
    i2c_delay();
}

// 产生I2C停止信号
void i2c_stop(void)
{
    SDA_PP(); // SDA设为输出
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_RESET);
    i2c_delay();

    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET); // SDA由低变高
    i2c_delay();
}

// 主机发送ACK信号
void i2c_ack(void)
{
    SDA_PP();                                         // SDA设为输出
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_RESET); // SDA拉低表示ACK
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET); // 释放SDA
    i2c_delay();
}

// 主机发送NACK信号
void i2c_nack(void)
{
    SDA_PP();                                         // SDA设为输出
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET); // SDA拉高表示NACK
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
    i2c_delay();
}

// 发送一个字节数据，返回从机应答状态
uint8_t sendbyte(uint8_t data)
{
    uint8_t i;
    uint8_t ack;

    SDA_PP(); // SDA设为输出

    // 发送8位数据，MSB先发送
    for (i = 0; i < 8; i++)
    {
        if (data & 0x80) // 发送数据的最高位
        {
            HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_SET);
        }
        else
        {
            HAL_GPIO_WritePin(I2C_PORT, SDA, GPIO_PIN_RESET);
        }
        data <<= 1; // 数据左移一位
        i2c_delay();

        HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高，从机读取数据
        i2c_delay();
        HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
        i2c_delay();
    }

    // 读取从机应答
    SDA_IN(); // SDA设为输入
    i2c_delay();
    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高
    i2c_delay();

    if (ReadSDA()) // 读取SDA状态
    {
        ack = 1; // 无应答
    }
    else
    {
        ack = 0; // 有应答
    }

    HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
    i2c_delay();

    return ack;
}

// 读取一个字节数据，ack=1发送ACK，ack=0发送NACK
uint8_t readbyte(uint8_t ack)
{
    uint8_t i;
    uint8_t data = 0;

    SDA_IN(); // SDA设为输入

    // 接收8位数据
    for (i = 0; i < 8; i++)
    {
        data <<= 1;                                     // 数据左移一位
        HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_SET); // SCL拉高
        i2c_delay();

        if (ReadSDA()) // 读取SDA状态
        {
            data |= 0x01; // 数据最低位置1
        }

        HAL_GPIO_WritePin(I2C_PORT, SCL, GPIO_PIN_RESET); // SCL拉低
        i2c_delay();
    }

    // 发送应答或非应答
    if (ack)
    {
        i2c_ack(); // 发送ACK
    }
    else
    {
        i2c_nack(); // 发送NACK
    }

    return data;
}

// I2C写数据
void i2c_write(uint8_t address7, uint8_t *data, uint8_t len)
{
    uint8_t i;

    i2c_start(); // 发送起始信号

    // 发送设备地址+写命令
    if (sendbyte((address7 << 1) | 0x00)) // 地址左移1位，最低位为0表示写操作
    {
        i2c_stop(); // 如果没有应答，发送停止信号
        return;
    }

    // 发送数据
    for (i = 0; i < len; i++)
    {
        if (sendbyte(data[i])) // 发送数据
        {
            i2c_stop(); // 如果没有应答，发送停止信号
            return;
        }
    }

    i2c_stop(); // 发送停止信号
}

// I2C读数据，返回0表示成功，1表示失败
uint8_t i2c_read(uint8_t address7, uint8_t *data, uint8_t len)
{
    uint8_t i;

    i2c_start(); // 发送起始信号

    // 发送设备地址+读命令
    if (sendbyte((address7 << 1) | 0x01)) // 地址左移1位，最低位为1表示读操作
    {
        i2c_stop(); // 如果没有应答，发送停止信号
        return 1;   // 返回失败
    }

    // 读取数据
    for (i = 0; i < len; i++)
    {
        if (i == (len - 1))
        {
            data[i] = readbyte(0); // 最后一个字节发送NACK
        }
        else
        {
            data[i] = readbyte(1); // 其他字节发送ACK
        }
    }

    i2c_stop(); // 发送停止信号
    return 0;   // 返回成功
}
